/*
TRABALHO 1 de COMPUTACAO 3 2007/2
Alunos:
 Anderson Marinho (DRE: 104042916)
 Artur Souza (DRE: 104020972)
*/

#ifndef GUARD_MINHA_MATRIZ_H
#define GUARD_MINHA_MATRIZ_H
#include <iostream>
#include "minha_matriz_exceptions.h"
#include <iterator>

using namespace std;

template<class T> class minha_matriz;

template<class T> class minha_matriz_linha {
private:
	int l;
	minha_matriz<T> * m;
public:
	minha_matriz_linha(int _l, minha_matriz<T> * _m) :
		l(_l), m(_m) {
	}
	T& operator[](int c) const {
		return (*m)(l, c);
	}
	int getLin() const {
		return l;
	}
	int getCol() const {
		return (*m).getCol();
	}
	friend ostream& operator <<(ostream& o, const minha_matriz_linha<T>& mml) {
		for (int j = 0; j < mml.getCol(); j++) {
			o << (j>0 ? " " : "")<< "("<< mml.getLin() << ","<< j << ")="
					<< mml[j];
		}
		return o;
	}
};

template<class T> class minha_matriz_coluna {
private:
	int c;
	minha_matriz<T> * m;
public:
	minha_matriz_coluna(int _c, minha_matriz<T> * _m) :
		c(_c), m(_m) {
	}
	T& operator()(int l) const {
		return (*m)(l, c);
	}
	T& operator[](int l) const {
		return (*m)(l, c);
	}
	int getLin() const {
		return (*m).getLin();
	}
	int getCol() const {
		return c;
	}
	friend ostream& operator <<(ostream& o, const minha_matriz_coluna<T>& mmc) {
		for (int j = 0; j < mmc.getLin(); j++) {
			o << (j>0 ? " " : "")<< "("<< j << ","<< mmc.getCol() << ")="
					<< mmc(j);
		}
		return o;
	}
};

template<class T> class minha_matriz {
private:
	T ** m;
	int lin;
	int col;

	void init() {
		for (int i = 0; i < lin; i++) {
			m[i] = new T[col];
		}
	}

	void free() {
		for (int i = 0; i < lin; i++) {
			delete[] m[i];
		}
		delete[] m;
	}

public:
	int getLin() const {
		return lin;
	}

	int getCol() const {
		return col;
	}

	minha_matriz(int n = 3) :
		m(new T*[n]), lin(n), col(n) {
		init();
	}

	minha_matriz(int _lin, int _col) :
		m(new T*[_lin]), lin(_lin), col(_col) {
		for (int i = 0; i < _lin; i++) {
			m[i] = new T[_col];
		}
	}

	~minha_matriz() {
		free();
	}

	minha_matriz(const minha_matriz<T>& mm) :
		m(new T*[mm.lin]), lin(mm.lin), col(mm.col) {
		init();
		for (int i = 0; i < mm.lin; i++) {
			for (int j = 0; j < mm.col; j++) {
				m[i][j] = mm.m[i][j];
			}
		}
	}

	friend ostream& operator <<(ostream& o, minha_matriz<T>& mm) {
		for (int i = 0; i < mm.getLin(); i++) {
			o << mm[i]<< endl;
		}
		return o;
	}

	friend minha_matriz<T> operator +(minha_matriz<T>& mm1,
			const minha_matriz<T>& mm2) {
		minha_matriz<T> mm(mm1.lin, mm1.col);
		if (mm1.lin != mm2.lin|| mm1.col != mm2.col)
			throw ExcecaoSomaMatrizesDiferentes();
		for (int i = 0; i < mm.lin; i++) {
			for (int j = 0; j < mm.col; j++) {
				mm.m[i][j] = mm1.m[i][j] + mm2.m[i][j];
			}
		}
		return mm;
	}

	friend minha_matriz<T> operator *(minha_matriz<T>& mm1,
			const minha_matriz<T>& mm2) {
		minha_matriz<T> mm(mm1.lin, mm2.col);
		if (mm1.col != mm2.lin)
			throw ExcecaoMultiplicacaoInvalidaDeMatrizes();

		for (int i = 0; i < mm1.lin; i++) {
			for (int j = 0; j < mm2.col; j++) {
				for (int k = 0; k < mm2.lin; k++) {
					mm.m[i][j] = mm.m[i][j] + mm1.m[i][k] * mm2.m[k][j];
				}
			}
		}
		return mm;
	}

	minha_matriz<T>& operator =(const minha_matriz<T>& mm2) {
		free();
		m = new T*[mm2.lin];
		lin = mm2.lin;
		col = mm2.col;
		init();
		for (int i = 0; i < lin; i++) {
			for (int j = 0; j < col; j++) {
				m[i][j] = mm2.m[i][j];
			}
		}
		return *this;
	}

	T& operator ()(int i, int j) {
		if ((i < 0) || (i >= lin)|| (j < 0)|| (j >= col)) {
			throw ExcecaoIndiceInvalidoDeMatriz();
		}
		return m[i][j];
	}

	minha_matriz_coluna<T> operator ()(int c) {
		return minha_matriz_coluna<T>(c, this);
	}

	minha_matriz_linha<T> operator [](int l) {
		return minha_matriz_linha<T>(l, this);
	}

	class Iterator : public std::iterator<std::forward_iterator_tag, T> {
public:
		Iterator(minha_matriz<T>* _m, int lin=0, int col=0) :
			itM(_m), itNLin(lin), itNCol(col) {
		}
		~Iterator() {
		}

		bool operator!=(const Iterator& other) {
			return (itNLin != other.itNLin);
		}

		Iterator& operator++() {
			itNCol = (itNCol+1)%itM->col;
			if (!itNCol)
				itNLin++;
			return (*this);
		}

		T operator*() {
			return itM->m[itNLin][itNCol];
		}

private:
		minha_matriz<T>* itM;
		int itNLin, itNCol;
	};

	class IteradorLinha : public std::iterator<std::forward_iterator_tag, T> {
public:
		IteradorLinha(minha_matriz<T>* _m, int lin=0) :
			itM(_m), itNLin(lin) {
		}
		~IteradorLinha() {
		}

		bool operator!=(const IteradorLinha& other) {
			return (itNLin != other.itNLin);
		}

		IteradorLinha& operator++() {
			itNLin++;
			return (*this);
		}

		minha_matriz_linha<T> operator*() {
			return minha_matriz_linha<T>(itNLin, itM);
		}

private:
		minha_matriz<T>* itM;
		int itNLin;
	};

	class IteradorColuna : public std::iterator<std::forward_iterator_tag, T> {
public:
		IteradorColuna(minha_matriz<T>* _m, int col=0) :
			itM(_m), itNCol(col) {
		}
		~IteradorColuna() {
		}

		bool operator!=(const IteradorColuna& other) {
			return (itNCol != other.itNCol);
		}

		IteradorColuna& operator++() {
			itNCol++;
			return (*this);
		}

		minha_matriz_coluna<T> operator*() {
			return minha_matriz_coluna<T>(itNCol, itM);
		}

private:
		minha_matriz<T>* itM;
		int itNCol;
	};

	Iterator begin() {
		return (Iterator(this));
	}

	Iterator end() {
		return (Iterator(this, lin, col));
	}

	IteradorLinha beginLinha() {
		return (IteradorLinha(this));
	}

	IteradorLinha endLinha() {
		return (IteradorLinha(this, lin));
	}

	IteradorColuna beginColuna() {
		return (IteradorColuna(this));
	}

	IteradorColuna endColuna() {
		return (IteradorColuna(this, col));
	}

};
#endif
